package com.tatesuke.automine.board.random;

import java.util.Formatter;
import java.util.Random;

import com.tatesuke.automine.board.Board;
import com.tatesuke.automine.board.BoardState;
import com.tatesuke.automine.board.CellState;

/**
 * ランダムに爆弾を配置するボードです。
 * 爆弾は最初の{@link #open(int, int)}時にランダムに配置されます。なお、初手で爆発することはありません。
 * @author tatesuke
 */
public class RandomBoard implements Board {

	private final int rowSize;
	private final int columnSize;
	private final int bombCount;

	private BoardState state;
	private boolean isBombCreated;
	private Cell[][] cells;

	private Random random;

	/**
	 * 与えられたパラメータでRandamBoardを構築します。
	 *
	 * @param rowSize 何行あるか(1以上)
	 * @param columnSize 何列あるか(1以上)
	 * @param bombCount 爆弾の数({@code rowSize * columnSize}未満)
	 */
	public RandomBoard(int rowSize, int columnSize, int bombCount) {
		validateRandomBoard(rowSize, columnSize, bombCount);

		this.rowSize = rowSize;
		this.columnSize = columnSize;
		this.bombCount = bombCount;

		reset();
	}
	@Override
	public int getRowSize() {
		return rowSize;
	}

	@Override
	public int getColumnSize() {
		return columnSize;
	}

	@Override
	public int getBombCount() {
		return bombCount;
	}
	@Override
	public BoardState getState() {
		return state;
	}

	@Override
	public void reset() {
		cells = createCells();

		state = BoardState.PLAYING;
		isBombCreated = false;
	}

	private Cell[][] createCells() {
		Cell[][] cells = new Cell[rowSize][columnSize];
		Cell[][] temp = new Cell[rowSize + 2][columnSize + 2];
		for (int i = 0; i < rowSize; i++) {
			for (int j = 0; j < columnSize; j++) {
				cells[i][j] = new Cell();
				temp[i + 1][j + 1] = cells[i][j];
			}
		}

		for (int i = 1; i <= rowSize; i++) {
			for (int j = 1; j <= columnSize; j++) {
				temp[i][j].addNeigbor(temp[i - 1][j]);
				temp[i][j].addNeigbor(temp[i - 1][j + 1]);
				temp[i][j].addNeigbor(temp[i][j + 1]);
				temp[i][j].addNeigbor(temp[i + 1][j + 1]);
				temp[i][j].addNeigbor(temp[i + 1][j]);
				temp[i][j].addNeigbor(temp[i + 1][j - 1]);
				temp[i][j].addNeigbor(temp[i][j - 1]);
				temp[i][j].addNeigbor(temp[i - 1][j - 1]);
			}
		}

		return cells;
	}

	@Override
	public CellState getCellState(int row, int column) {
		validateState();

		CellState cellType;

		if (!isValidRange(row, column)) {
			cellType = CellState.SENTRY;
		} else {
			cellType = cells[row][column].getType();
		}

		return cellType;
	}

	@Override
	public void open(int row, int column) {
		validateState();
		validateRange(row, column);

		if (!isBombCreated) {
			createBombs(row, column);
		}

		cells[row][column].open();

		if (cells[row][column].isBurn()) {
			state = BoardState.BURN;
		} else if ((rowSize * columnSize - countOpenCells()) == bombCount) {
			state = BoardState.CLEAR;
		}
	}

	@Override
	public void openAllNeighbors(int row, int column) {
		validateState();
		validateRange(row, column);

		Cell cell = cells[row][column];
		if (cell.getType() == CellState.OPEN ) {
			int neighborFlagCount = cell.countNeighbor(CellState.FLAG);
			if (neighborFlagCount == cell.getHint()) {
				cell.openAllNeighbors();
			}
		}
	}

	private int countOpenCells() {
		int count = 0;

		for (int row = 0; row < rowSize; row++) {
			for (int column = 0; column < columnSize; column++) {
				if (cells[row][column].getType() == CellState.OPEN) {
					count++;
				}
			}
		}

		return count;
	}

	@Override
	public void toggleFlag(int row, int column) {
		validateState();

		cells[row][column].toggleFlag();
	}

	@Override
	public int getHint(int row, int column) {
		validateState();
		validateRange(row, column);
		if (getCellState(row, column) != CellState.OPEN) {
			throw new IllegalStateException(format("開いていないマスに対してメソッドを呼び出した(%d, %d)", row, column));
		}

		return cells[row][column].getHint();
	}

	/**
	 * 盤面生成に使用するRandomを設定します。
	 * @param random 盤面生成に使用するRandom
	 * @throws IllegalArgumentException 引数にnullを渡した場合
	 */
	public void setRandom(Random random) {
		if (random == null) {
			throw new IllegalArgumentException("引数にnullは渡せません。");
		}

		this.random = random;
	}

	public String getDebugPrint() {
		StringBuilder buffer = new StringBuilder();

		for (int row = 0; row < rowSize; row++) {
			for (int column = 0; column < columnSize; column++) {
				if (cells[row][column].isBomb) {
					buffer.append(" B");
				} else {
					int hint = cells[row][column].getHint();
					buffer.append(" ").append(hint);
				}
			}
			buffer.append("\n");
		}

		return buffer.toString();
	}


	/**
	 * 盤面を作る。引数で指定した座標には爆弾を置かない。
	 */
	private void createBombs(int row, int column) {
		if (random == null) {
			random = new Random();
		}

		for (int temp = 0; temp < bombCount; temp++) {
			int i, j;
			do {
				i = random.nextInt(rowSize);
				j = random.nextInt(columnSize);
			} while (cells[i][j].isBomb || ((i == row) && (j == column)));
			cells[i][j].setBomb(true);
		}

		isBombCreated = true;
	}

	/**
	 * クリアしてないこと。爆発してないこと。
	 */
	private void validateState() {
		if (state == BoardState.CLEAR) {
			throw new IllegalStateException("クリア状態でこのメソッドは呼び出せない。");
		}
		if (state == BoardState.BURN) {
			throw new IllegalStateException("爆発状態でこのメソッドは呼び出せない。");
		}
	}

	/**
	 * formatするよ。printfとかみたいなあれ。
	 */
	private String format(String format, Object... args) {
		Formatter formatter = new Formatter();
		return formatter.format(format, args).toString();
	}

	/**
	 * <ul>
	 * 	<li>行サイズは1以上でなければならない
	 * 	<li>列サイズは1以上でなければならない
	 * 	<li>爆弾の数は1以上でなければならない
	 * 	<li>爆弾の数は(行サイズ×列サイズ）未満でなければならない
	 * </ul>
	 */
	private void validateRandomBoard(int rowSize, int columnSize, int bombCount) {
		if (!(1 <= rowSize)) {
			throw new IllegalArgumentException(
					format("行サイズは1以上でなければならない(%d)", rowSize));
		}
		if (!(1 <= columnSize)) {
			throw new IllegalArgumentException(
					format("列サイズは1以上でなければならない(%d)", columnSize));
		}
		if (!(1 <= bombCount)) {
			throw new IllegalArgumentException(
					format("爆弾の数は1以上でなければならない(%d)", bombCount));
		}
		if (!(bombCount < (rowSize * columnSize))) {
			throw new IllegalArgumentException(
					format("爆弾の数は(行サイズ×列サイズ）未満でなければならない(%d*%d,%d)", rowSize, columnSize, bombCount));
		}
	}

	/**
	 * 範囲バリデーション
	 */
	private void validateRange(int row, int column) {
		if (!isValidRange(row, column)) {
			throw new IndexOutOfBoundsException(
					format("ボードの大きさが%d, %dなのに対し、%d, %dを参照しようとした。", rowSize, columnSize, row, column));
		}
	}

	/**
	 * 範囲チェック
	 */
	private boolean isValidRange(int row, int column) {
		return 0 <= row
				&& row < rowSize
				&& 0 <= column
				&& column < columnSize;
	}

}
